package uk.co.hs.web.initializer;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.OptionHelper;
import ch.qos.logback.core.util.StatusPrinter;

/**
 * In spring there is a Log4jConfigListener which can be used to reconfigure the default location of the log4j config. I always liked to use it because so I could relocate my log4j.xml from the classes directory next to the web.xml so the config files were at one place. Because I did not find something like that I wrote one for logback. I think it could be included into logback-classic.
 *
 * ch.qos.logback.classic.servlet.LogbackConfigListener configuration is very easy:
 *
 * 1. Add the listener to the web.xml (it should be the first listener so logback gets configured before the other listeners start up):
 * <listener>
 *  <listener-class>ch.qos.logback.classic.servlet.LogbackConfigListener</listener-class>
 * </listener>
 *
 * 2. Add a context-param to web.xml to specify the location of the logback config:
 * <context-param>
 *   <param-name>logbackConfigLocation</param-name>
 *  <param-value>/WEB-INF/log.xml</param-value>
 * </context-param>

 * And thats it. The location is resolved in the following way (similar to spring aproach):
 * - if the location starts with "/" it is resolved as servlet context resource
 * - otherwise an URL is constructed from it. So you can use locations like this also: file:/foo/bar/logback.xml or file:d:/foo/bar/logback.xml
 * - if the the first method fails a java.io.FileInputStream is created from the location to be used as input for logback configuration.
 * - If none of the above worked an error is logged to servlet log but no exception is thrown.

 * The location can contain system properties, those gets resolved. So a location can be configured for example to: file:${user.home}/configuration/log.xml
 *
 * The original spring listener only worked with expanded wars this logback variant does not have this requirement.
 * {@link ServletContextListener} that can be used in web applications to define
 * the location of the logback configuration.
 *
 * <p>
 * Courtesy of Attila Király see LOGBACK-557 problem, nice one  Attila
 * Should be the first listener to configure logback before using it. Location
 * is defined in the <code>logbackConfigLocation</code> context param.
 * Placeholders (ex: ${user.home}) are supported. Location examples:<br />
 * /WEB-INF/log-sc.xml -> loaded from servlet context<br />
 * classpath:foo/log-cp.xml -> loaded from classpath<br />
 * file:/D:/log-absfile.xml -> loaded as url<br />
 * D:/log-absfile.xml -> loaded as absolute file<br />
 * log-relfile.xml -> loaded as file relative to the servlet container working
 * directory<br />
 * </p>
 */
public class LogbackInitializerListener implements ServletContextListener
{

  /**
   * Context param name.
   */

  private static final String VALUE = "HealthAndSafety_rootFolder";

  private static final String JAVA_CONTEXT = "java:comp/env";

  private static final String LOCATION = "LogbackConfigLocation";

  private static final String FILE_SEPARATOR = System.getProperty("file.separator");

  /**
   * Prefix for classpath urls.
   */
  public static final String LOCATION_PREFIX_CLASSPATH = "classpath:";
  /**
   * Called by container
   *@see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
   *{@inheritDoc}
   */
  public void contextInitialized(ServletContextEvent sce)
  {
    configureLogback(sce.getServletContext());
  }
  /**
   * Logback configuration
   *@param aSc
   */
  public void configureLogback(ServletContext aSc)
  {
    try
    {

      InitialContext ic = new InitialContext();

      Context ctx = (Context) ic.lookup(JAVA_CONTEXT);
      StringBuilder buf = new StringBuilder();
      String envEntrylocation = ((String) (ctx.lookup(LOCATION))).trim();

      if ((envEntrylocation != null) && (!envEntrylocation.equals("")))
      {
        String first = envEntrylocation.substring(0, 1);
        if (FILE_SEPARATOR.equals(first))
        {
          String pathValue = ((String) (ctx.lookup(VALUE))).trim();
          buf.append(pathValue).append(envEntrylocation);
        }
        else
        {
          buf.append(envEntrylocation);
        }
      }
      else
      {
        String pathValue = ((String) (ctx.lookup(VALUE))).trim();
        buf.append(pathValue).append(FILE_SEPARATOR).append("config");
      }
      ILoggerFactory ilc = LoggerFactory.getILoggerFactory();
      String location = buf.toString();
      if (!(ilc instanceof LoggerContext))
      {
        aSc.log("Can not configure logback. " + LoggerFactory.class + " is using " + ilc + " which is not an instance of " + LoggerContext.class);
        return;
      }

      LoggerContext lc = (LoggerContext) ilc;
      if (location != null)
        location = OptionHelper.substVars(location, lc);

      if (location == null)
      {
        aSc.log("Can not configure logback. Location is null." + " Maybe env-entry \"" + LOCATION + "\" is not set or is not correct.");
        return;
      }

      URL url = toUrl(aSc, location);

      if (url == null)
      {
        aSc.log("Can not configure logback. Could not find logback" + " config neither as servlet context-, nor as"
            + " classpath-, nor as url-, nor as file system" + " resource. Config location = \"" + location + "\".");
        return;
      }

      aSc.log("Configuring logback. Config location = \"" + location + "\", full url = \"" + url + "\".");

      configure(aSc, url, lc);
    }
    catch (NamingException e)
    {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  protected void configure(ServletContext sc,
                           URL location,
                           LoggerContext lc)
  {
    JoranConfigurator configurator = new JoranConfigurator();
    configurator.setContext(lc);
    lc.stop();
    try
    {
      configurator.doConfigure(location);
    }
    catch (JoranException e)
    {
      sc.log("Failed to configure logback.", e);
    }
    StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
  }

  protected URL toUrl(ServletContext sc,
                      String location)
  {
    URL url = null;

    if (location.startsWith("/"))
      try
      {
        url = sc.getResource(location);
      }
      catch (MalformedURLException e1)
      {
        // NO-OP
      }

    if (url == null && location.startsWith(LOCATION_PREFIX_CLASSPATH))
      url = Thread.currentThread().getContextClassLoader().getResource(location.substring(LOCATION_PREFIX_CLASSPATH.length()));

    if (url == null)
      try
      {
        url = new URL(location);
      }
      catch (MalformedURLException e)
      {
        // NO-OP
      }

    if (url == null)
    {
      File file = new File(location);
      if (!file.isAbsolute())
        file = file.getAbsoluteFile();
      if (file.isFile())
        try
        {
          url = file.toURI().normalize().toURL();
        }
        catch (MalformedURLException e)
        {
          // NO-OP
        }
    }

    return url;
  }
  /**
   * Called by the container
   *@see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
   *{@inheritDoc}
   */
  public void contextDestroyed(ServletContextEvent sce)
  {
    ILoggerFactory ilc = LoggerFactory.getILoggerFactory();

    if (!(ilc instanceof LoggerContext))
      return;
    //deregister to avoid memory leaks esp thro JMX
    LoggerContext lc = (LoggerContext) ilc;
    lc.stop();

  }
}